package top.cardone.context;


import com.google.common.base.Charsets;
import com.google.common.collect.Lists;
import groovy.lang.GroovyClassLoader;
import groovy.util.ResourceException;
import lombok.Getter;
import lombok.val;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.io.Resource;
import top.cardone.cache.Cache;
import top.cardone.core.util.action.Action1;
import top.cardone.core.util.func.Func0;
import top.cardone.core.util.func.Func1;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

/**
 * 上下文支持
 *
 * @author yao hai tao
 */
@lombok.extern.log4j.Log4j2
public class ApplicationContextHolder {
    /**
     * Spring ApplicationContext
     */
    @Getter
    private static ApplicationContext applicationContext;

    private static List<String> groovyScriptRoots = Lists.newArrayList();

    private static GroovyClassLoader groovyClassLoader = new GroovyClassLoader();

    private static String cacheBeanName = "cardone.bean.cache";

    @Autowired
    public void setApplicationContext(ConfigurableApplicationContext configurableApplicationContext) {
        ApplicationContextHolder.applicationContext = configurableApplicationContext;
    }

    public void setGroovyScriptRoots(List<String> groovyScriptRoots) {
        ApplicationContextHolder.groovyScriptRoots = groovyScriptRoots;
    }

    public void setGroovyClassLoader(GroovyClassLoader groovyClassLoader) {
        ApplicationContextHolder.groovyClassLoader = groovyClassLoader;
    }

    public void setCacheBeanName(String cacheBeanName) {
        ApplicationContextHolder.cacheBeanName = cacheBeanName;
    }

    /**
     * 执行有返回值
     *
     * @param requiredType 类型
     * @param action       执行方法
     * @param names        名称
     * @param <T1>         bean 类型
     * @return
     */
    public static <T1> void action(Class<T1> requiredType, Action1<T1> action, String... names) {
        action(requiredType, action, null, names);
    }

    /**
     * 执行有返回值
     *
     * @param requiredType 类型
     * @param action       执行方法
     * @param names        名称
     * @param <T1>         bean 类型
     * @return
     */
    public static <T1> void action(Class<T1> requiredType, Action1<T1> action, Action1<T1> defaultAction, String... names) {
        T1 t1 = getBean(requiredType, names);

        if (t1 != null) {
            action.action(t1);

            return;
        }

        if (defaultAction != null) {
            defaultAction.action(t1);
        }
    }

    /**
     * 执行有返回值
     *
     * @param names 名称
     * @return
     */
    public static void action(Action1<Object> action, String... names) {
        action(Object.class, action, null, names);
    }

    /**
     * 执行有返回值
     *
     * @param names 名称
     * @return
     */
    public static void action(Action1<Object> action, Action1<Object> defaultAction, String... names) {
        action(Object.class, action, defaultAction, names);
    }

    /**
     * 执行有返回值
     *
     * @param requiredType 类型
     * @param func         执行方法
     * @param names        名称
     * @param <R1>         返回类型
     * @param <T1>         bean 类型
     * @return
     */
    public static <R1, T1> R1 func(Class<T1> requiredType, Func1<R1, T1> func, String... names) {
        return func(requiredType, func, null, names);
    }

    /**
     * 执行有返回值
     *
     * @param requiredType 类型
     * @param func         执行方法
     * @param names        名称
     * @param <R1>         返回类型
     * @param <T1>         bean 类型
     * @return
     */
    public static <R1, T1> R1 func(Class<T1> requiredType, Func1<R1, T1> func, Func0<R1> defaultFunc, String... names) {
        T1 t1 = getBean(requiredType, names);

        if (t1 != null) {
            return func.func(t1);
        }

        if (defaultFunc != null) {
            return defaultFunc.func();
        }

        return null;
    }

    /**
     * 执行有返回值
     *
     * @param func  执行方法
     * @param names 名称
     * @param <R1>  返回类型
     * @return
     */
    public static <R1> R1 func(Func1<R1, Object> func, String... names) {
        return func(Object.class, func, null, names);
    }

    /**
     * 执行有返回值
     *
     * @param func  执行方法
     * @param names 名称
     * @param <R1>  返回类型
     * @return
     */
    public static <R1> R1 func(Func1<R1, Object> func, Func0<R1> defaultFunc, String... names) {
        return func(Object.class, func, defaultFunc, names);
    }

    /**
     * 获取 bean
     *
     * @param requiredType 类型
     * @param names        名称
     * @return bean
     */
    public static <T> T getBean(final Class<T> requiredType, String... names) {
        return getBean(requiredType, false, names);
    }

    public static <T> T getBean(List<String> groovyScriptRoots, String... scriptNames) {
        if (CollectionUtils.isEmpty(groovyScriptRoots)) {
            return null;
        }

        for (String groovyScriptRoot : groovyScriptRoots) {
            for (val scriptName : scriptNames) {
                try {
                    String filePath = StringUtils.join(groovyScriptRoot, scriptName);

                    Resource resource = getResource(filePath);

                    if (!resource.exists()) {
                        continue;
                    }

                    String text;

                    try (InputStream is = resource.getInputStream()) {
                        text = IOUtils.toString(is, Charsets.UTF_8);
                    }

                    Class scriptClass = groovyClassLoader.parseClass(text);

                    if (scriptClass == null) {
                        continue;
                    }

                    T t = (T) scriptClass.newInstance();

                    if (t != null) {
                        applicationContext.getAutowireCapableBeanFactory().autowireBean(t);
                    }

                    return t;
                } catch (Exception e) {
                    if (e instanceof ResourceException) {
                        log.debug(e);
                    } else {
                        log.error(e);
                    }
                }
            }
        }

        return null;
    }

    public static <T> T getBean(List<String> groovyScriptRoots, String[] scriptNames, boolean cache) {
        if (!cache) {
            return getBean(groovyScriptRoots, scriptNames);
        }

        if (!applicationContext.containsBean(cacheBeanName)) {
            return getBean(groovyScriptRoots, scriptNames);
        }

        Cache cacheBean = applicationContext.getBean(cacheBeanName, Cache.class);

        if (cacheBean == null) {
            return getBean(groovyScriptRoots, scriptNames);
        }

        return cacheBean.get(ApplicationContextHolder.class.getClass().getName() + ".getBean",
                1, scriptNames, () -> getBean(groovyScriptRoots, scriptNames));
    }

    /**
     * 获取 bean
     *
     * @param requiredType 类型
     * @param append       附加
     * @param names        名称
     * @param <T>
     * @return
     */
    public static <T> T getBean(final Class<T> requiredType, boolean append, String... names) {
        if (ArrayUtils.isEmpty(names)) {
            names = applicationContext.getBeanNamesForType(requiredType);

            if (ArrayUtils.isEmpty(names)) {
                throw new RuntimeException("未找到符合类型的bean:" + requiredType.getName());
            }

            if (ArrayUtils.contains(names, requiredType.getName())) {
                names = new String[]{requiredType.getName()};
            }
        }

        for (String name : names) {
            if (StringUtils.isBlank(name)) {
                name = requiredType.getName();
            }

            if (append) {
                name = requiredType.getName() + name;
            }

            if (applicationContext.containsBean(name)) {
                return applicationContext.getBean(name, requiredType);
            }

            T t = getBean(groovyScriptRoots, new String[]{name + ".groovy"}, !log.isDebugEnabled());

            if (t != null) {
                return t;
            }
        }

        if (log.isDebugEnabled()) {
            if (ArrayUtils.isEmpty(names)) {
                log.warn("未找到对应的bean:" + requiredType.getName());
            } else {
                for (String name : names) {
                    if (log.isWarnEnabled()) {
                        if (append) {
                            log.warn("未找到对应的bean:" + requiredType.getName() + name);
                        } else {
                            log.warn("未找到对应的bean:" + name);
                        }
                    }
                }
            }
        }

        return null;
    }

    /**
     * 获取 bean
     *
     * @param names 名称集合
     * @return bean
     */
    public static Object getBean(final String... names) {
        return getBean(Object.class, names);
    }

    public static Resource getResource(String locationPattern) {
        try {
            Resource[] resources = applicationContext.getResources(locationPattern);

            for (Resource resource : resources) {
                if (resource.exists()) {
                    return resource;
                }
            }
        } catch (IOException e) {
            log.error(e);
        }

        return applicationContext.getResource(locationPattern);
    }
}